import * as THREE from 'three';
import {  Object3D, Vector3 } from 'three';

const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };

class EditorControls extends THREE.EventDispatcher {

  enabled:boolean;
  center:Vector3;
  panSpeed:number;
  zoomSpeed:number;
  rotationSpeed:number;

  private changeEvent = { type: 'change' };
  private state = STATE.NONE;

  private vector = new THREE.Vector3();
  private delta = new THREE.Vector3();
  private box = new THREE.Box3();

  private normalMatrix = new THREE.Matrix3();
	private pointer = new THREE.Vector2();
	private pointerOld = new THREE.Vector2();
	private spherical = new THREE.Spherical();
	private sphere = new THREE.Sphere();

  private object:Object3D;
  private domElement:HTMLElement;


  private touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
  private prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];

  private prevDistance:number|null = null;

	constructor( object:Object3D, domElement:HTMLElement ) {

		super();
    this.object=object;
    this.domElement=domElement;
		// API

		this.enabled = true;
		this.center = new THREE.Vector3();
		this.panSpeed = 0.002;
		this.zoomSpeed = 0.1;
		this.rotationSpeed = 0.005;

		// internals

		var scope = this;

		
		

		//

		domElement.addEventListener( 'touchstart', this.touchStart.bind(this), { passive: false } );
		domElement.addEventListener( 'touchmove', this.touchMove.bind(this), { passive: false } );

    
    domElement.addEventListener( 'contextmenu', this.contextmenu.bind(this) );
    domElement.addEventListener( 'dblclick', this.onMouseUp.bind(this) );
    domElement.addEventListener( 'wheel', this.onMouseWheel.bind(this), { passive: false } );

    domElement.addEventListener( 'pointerdown', this.onPointerDown.bind(this) );

    // touch

	}

  // events
    
  focus( target:Object3D ) {

    var distance;

    this.box.setFromObject( target );

    if ( this.box.isEmpty() === false ) {

      this.box.getCenter( this.center );
      distance = this.box.getBoundingSphere( this.sphere ).radius;

    } else {

      // Focusing on an Group, AmbientLight, etc

      this.center.setFromMatrixPosition( target.matrixWorld );
      distance = 0.1;

    }

    this.delta.set( 0, 0, 1 );
    this.delta.applyQuaternion( this.object.quaternion );
    this.delta.multiplyScalar( distance * 4 );

    this.object.position.copy( this.center ).add( this.delta );

    this.dispatchEvent( this.changeEvent );

  }

  pan( delta:Vector3 ) {

    var distance = this.object.position.distanceTo( this.center );

    delta.multiplyScalar( distance * this.panSpeed );
    delta.applyMatrix3( this.normalMatrix.getNormalMatrix( this.object.matrix ) );

    this.object.position.add( delta );
    this.center.add( delta );

    this.dispatchEvent( this.changeEvent );

  }

  zoom( delta:Vector3 ) {

    var distance = this.object.position.distanceTo( this.center );

    delta.multiplyScalar( distance * this.zoomSpeed );

    if ( delta.length() > distance ) return;

    delta.applyMatrix3( this.normalMatrix.getNormalMatrix( this.object.matrix ) );

    this.object.position.add( delta );

    this.dispatchEvent( this.changeEvent );

  }

  rotate( delta:Vector3 ) {

    this.vector.copy( this.object.position ).sub( this.center );

    this.spherical.setFromVector3( this.vector );

    this.spherical.theta += delta.x * this.rotationSpeed;
    this.spherical.phi += delta.y * this.rotationSpeed;

    this.spherical.makeSafe();

    this.vector.setFromSpherical( this.spherical );

    this.object.position.copy( this.center ).add( this.vector );

    this.object.lookAt( this.center );

    this.dispatchEvent( this.changeEvent );

  }

  onPointerDown( event:PointerEvent ) {

    if ( this.enabled === false ) return;

    switch ( event.pointerType ) {

      case 'mouse':
      case 'pen':
        this.onMouseDown( event );
        break;

      // TODO touch

    }

    this.domElement.ownerDocument.addEventListener( 'pointermove',this.onPointerMove.bind(this) );
    this.domElement.ownerDocument.addEventListener( 'pointerup',this.onPointerUp.bind(this) );

  }

  onPointerMove( event:PointerEvent ) {

    if ( this.enabled === false ) return;

    switch ( event.pointerType ) {

      case 'mouse':
      case 'pen':
        this.onMouseMove( event );
        break;

      // TODO touch

    }

  }

  onPointerUp( event:PointerEvent ) {

    switch ( event.pointerType ) {

      case 'mouse':
      case 'pen':
        this.onMouseUp();
        break;

      // TODO touch

    }

    this.domElement.ownerDocument.removeEventListener( 'pointermove', this.onPointerMove.bind(this) );
    this.domElement.ownerDocument.removeEventListener( 'pointerup', this.onPointerUp.bind(this) );

  }

  // mouse

  onMouseDown( event:MouseEvent ) {

    if ( event.button === 0 ) {

      this.state = STATE.ROTATE;

    } else if ( event.button === 1 ) {

      this.state = STATE.ZOOM;

    } else if ( event.button === 2 ) {

      this.state = STATE.PAN;

    }

    this.pointerOld.set( event.clientX, event.clientY );

  }

  onMouseMove( event:MouseEvent ) {

    this.pointer.set( event.clientX, event.clientY );

    var movementX = this.pointer.x - this.pointerOld.x;
    var movementY = this.pointer.y - this.pointerOld.y;

    if ( this.state === STATE.ROTATE ) {

      this.rotate( this.delta.set( - movementX, - movementY, 0 ) );

    } else if ( this.state === STATE.ZOOM ) {

      this.zoom( this.delta.set( 0, 0, movementY ) );

    } else if ( this.state === STATE.PAN ) {

      this.pan( this.delta.set( - movementX, movementY, 0 ) );

    }

    this.pointerOld.set( event.clientX, event.clientY );

  }

  onMouseUp() {

    this.state = STATE.NONE;

  }

  onMouseWheel( event:WheelEvent ) {

    if ( this.enabled === false ) return;

    event.preventDefault();

    // Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460
    this.zoom( this.delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) );

  }

  contextmenu( event:Event ) {

    event.preventDefault();

  }

  dispose() {

    this.domElement.removeEventListener( 'contextmenu', this.contextmenu.bind(this) );
    this.domElement.removeEventListener( 'dblclick', this.onMouseUp.bind(this) );
    this.domElement.removeEventListener( 'wheel', this.onMouseWheel.bind(this) );

    this.domElement.removeEventListener( 'pointerdown', this.onPointerDown.bind(this) );

    this.domElement.removeEventListener( 'touchstart', this.touchStart.bind(this) );
    this.domElement.removeEventListener( 'touchmove', this.touchMove.bind(this) );

    

  }


  touchStart( event:TouchEvent ) {

    if ( this.enabled === false ) return;

    switch ( event.touches.length ) {

      case 1:
        this.touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        break;

      case 2:
        this.touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.prevDistance = this.touches[ 0 ].distanceTo( this.touches[ 1 ] );
        break;

    }

    this.prevTouches[ 0 ].copy( this.touches[ 0 ] );
    this.prevTouches[ 1 ].copy( this.touches[ 1 ] );

  }


  touchMove( event:TouchEvent ) {

    if ( this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();
    const self=this;

    function getClosest( touch:Vector3, touches:Vector3[] ) {

      var closest = self.touches[ 0 ];

      for ( var touch2 of touches ) {

        if ( closest.distanceTo( touch ) > touch2.distanceTo( touch ) ) closest = touch2;

      }

      return closest;

    }

    switch ( event.touches.length ) {

      case 1:
        this.touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.rotate( this.touches[ 0 ].sub( getClosest( this.touches[ 0 ], this.prevTouches ) ).multiplyScalar( - 1 ) );
        break;

      case 2:
        this.touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        this.touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
        var distance = this.touches[ 0 ].distanceTo( this.touches[ 1 ] );
        if(this.prevDistance!==null){
          this.zoom( this.delta.set( 0, 0, this.prevDistance - distance ) );
        }
        
        this.prevDistance = distance;


        var offset0 = this.touches[ 0 ].clone().sub( getClosest( this.touches[ 0 ], this.prevTouches ) );
        var offset1 = this.touches[ 1 ].clone().sub( getClosest( this.touches[ 1 ], this.prevTouches ) );
        offset0.x = - offset0.x;
        offset1.x = - offset1.x;

        this.pan( offset0.add( offset1 ) );

        break;

    }

    this.prevTouches[ 0 ].copy( this.touches[ 0 ] );
    this.prevTouches[ 1 ].copy( this.touches[ 1 ] );

  }

}

export { EditorControls };